<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" >

<title>JavaScript 之 Promise | 迷航</title>
<meta name="description" content="人可以迷失方向，但是不能停下脚步！">

<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">

<link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.7.2/css/all.css" integrity="sha384-fnmOCqbTlWIlj8LyTjo7mOUStjsKC4pOpQbqyi7RrhN7udi9RwhKkMHpvLbHG9Sr" crossorigin="anonymous">
<link rel="shortcut icon" href="https://tingke.gitee.io/post/favicon.ico?v=1690805106645">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/KaTeX/0.10.0/katex.min.css">
<link rel="stylesheet" href="https://tingke.gitee.io/post/styles/main.css">



<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script src="https://cdn.bootcss.com/highlight.js/9.12.0/highlight.min.js"></script>

<link rel="stylesheet" href="https://unpkg.com/aos@next/dist/aos.css" />



  </head>
  <body>
    <div id="app" class="main">

      <div class="sidebar" :class="{ 'full-height': menuVisible }">
  <div class="top-container" data-aos="fade-right">
    <div class="top-header-container">
      <a class="site-title-container" href="https://tingke.gitee.io/post">
        <img src="https://tingke.gitee.io/post/images/avatar.png?v=1690805106645" class="site-logo">
        <h1 class="site-title">迷航</h1>
      </a>
      <div class="menu-btn" @click="menuVisible = !menuVisible">
        <div class="line"></div>
      </div>
    </div>
    <div>
      
        
          <a href="/" class="site-nav">
            首页
          </a>
        
      
        
          <a href="/archives" class="site-nav">
            归档
          </a>
        
      
        
          <a href="/tags" class="site-nav">
            标签
          </a>
        
      
        
          <a href="/post/about" class="site-nav">
            关于
          </a>
        
      
    </div>
  </div>
  <div class="bottom-container" data-aos="flip-up" data-aos-offset="0">
    <div class="social-container">
      
        
      
        
      
        
      
        
      
        
      
    </div>
    <div class="site-description">
      人可以迷失方向，但是不能停下脚步！
    </div>
    <div class="site-footer">
      Powered by <a href="https://github.com/getgridea/gridea" target="_blank">Gridea</a> Copyright © 2019 | <a class="rss" href="https://tingke.gitee.io/post/atom.xml" target="_blank">RSS</a>
    </div>
  </div>
</div>


      <div class="main-container">
        <div class="content-container" data-aos="fade-up">
          <div class="post-detail">
            <h2 class="post-title">JavaScript 之 Promise</h2>
            <div class="post-date">2020-12-29</div>
            
            <div class="post-content">
              <p><code>ECMAscript 6</code> 原生提供了 <code>Promise</code> 对象。<br>
有了 <code>Promise</code> 对象，就可以将异步操作以同步操作的流程表达出来，避免了层层嵌套的回调函数（嵌套地狱）。此外，<code>Promise</code> 对象提供统一的接口，使得控制异步操作更加容易。如下：</p>
<pre><code class="language-js">// 创建一个Promise实例
var promise = new Promise(function(resolve, reject) {
    // 异步处理
    // 处理结束后、调用resolve 或 reject
});

// then方法
promise.then((value) =&gt; {
// 成功处理
}, (error) =&gt; {
// 失败处理
})
</code></pre>
<p>下面是一个用 <code>Promise</code> 对象实现的 <code>Ajax</code> 操作的例子</p>
<pre><code class="language-js">function ajax(URL) {
    return new Promise(function (resolve, reject) {
        var req = new XMLHttpRequest(); 
        req.open('GET', URL, true);
        req.onload = function () {
        if (req.status === 200) { 
                resolve(req.responseText);
            } else {
                reject(new Error(req.statusText));
            } 
        };
        req.onerror = function () {
            reject(new Error(req.statusText));
        };
        req.send(); 
    });
}

ajax('test').then(function onFulfilled(value){
    console.log('内容是：' + value); 
}).catch(function onRejected(error){
    console.log('错误：' + error); 
});
</code></pre>
<p><code>Promise</code> 还可以做更多的事情，比如，有若干个异步任务，需要先做任务1，如果成功后再做任务2，任何任务失败则不再继续并执行错误处理函数。</p>
<p>要串行执行这样的异步任务，不用 <code>Promise</code> 需要写一层一层的嵌套代码。有了 <code>Promise</code>，我们只需要简单地写：</p>
<pre><code class="language-js">job1.then(job2).then(job3).catch(handleError);
// 其中，job1、job2和job3都是Promise对象
</code></pre>
<p>Promise也有一些缺点:</p>
<ol>
<li>无法取消 <code>Promise</code>，一旦新建它就会立即执行，无法中途取消。</li>
<li>如果不设置回调函数，<code>Promise</code> 内部抛出的错误，不会反应到外部</li>
<li>当处于 <code>Pending</code> 状态时，无法得知目前进展到哪一个阶段（刚刚开始还是即将完成）</li>
</ol>
<h2 id="promiseall">Promise.all</h2>
<p><code>Promise.all</code> 可以将多个 <code>Promise</code> 实例包装成一个新的 <code>Promise</code> 实例。同时，成功和失败的返回值是不同的，成功的时候返回的是一个结果数组，而失败的时候则返回最先被 <code>reject</code> 失败状态的值。</p>
<pre><code class="language-js">let wake = (time) =&gt; {
  return new Promise((resolve, reject) =&gt; {
    setTimeout(() =&gt; {
      resolve(`${time / 1000}秒后醒来`)
    }, time)
  })
}

let p1 = wake(3000)
let p2 = wake(2000)

Promise.all([p1, p2]).then((result) =&gt; {
  console.log(result)       // [ '3秒后醒来', '2秒后醒来' ]
}).catch((error) =&gt; {
  console.log(error)
})
</code></pre>
<h2 id="promiserace">Promise.race</h2>
<p>与 <code>Promise.all</code> 用法一样，只不过返回的结果为回调最快的那个。</p>
<pre><code class="language-js">let p1 = new Promise((resolve, reject) =&gt; {
  setTimeout(() =&gt; {
    resolve('success')
  },1000)
})

let p2 = new Promise((resolve, reject) =&gt; {
  setTimeout(() =&gt; {
    reject('failed')
  }, 500)
})

Promise.race([p1, p2]).then((result) =&gt; {
  console.log(result)
}).catch((error) =&gt; {
  console.log(error)  // 打开的是 'failed'
})
</code></pre>
<h3 id="常见面试题">常见面试题</h3>
<pre><code class="language-js">// 宏任务与微任务
let promise = new Promise(function(resolve, reject){
    console.log(&quot;1&quot;);
    resolve();
});
setTimeout(()=&gt;console.log(&quot;2&quot;), 0);
promise.then(() =&gt; console.log(&quot;3&quot;));
console.log(&quot;4&quot;);

// 1
// 4
// 3
// 2
</code></pre>
<h2 id="手撸-promise">手撸 Promise</h2>
<p>根据 <a href="https://promisesaplus.com/">Promise/A++</a>规范，手撸一个。</p>
<pre><code class="language-js">// 2.1 Promise States
const PENDING   = 'PENDING',
      FULFILLED = 'FULFILLED',
      REJECTED  = 'REJECTED';

// 2.3 处理x的值（普通值/Promise）
function resolvePromise(promise2, x, resolve, reject) {
    if(promise2 == x) {
        reject(new TypeError(`Chaining cycle detected 
        for promise #&lt;${promise2.class} &gt;`));
    }

    let called = false;

    if((typeof x === 'object' &amp;&amp; x !== null) 
    || typeof x === 'function') {
        try {
            let then = x.then;
            if(typeof then == 'function') {
                then.call(x, (y) =&gt; {
                    if(called) return;
                    called = true;
                    resolvePromise(promise2, y, resolve, reject);
                }, (r) =&gt; {
                    if(called) return;
                    called = true;
                    reject(r);
                })
            }else {
                resolve(x);
            }
        } catch (e) {
            if(called) return;
            called = true;
            reject(e);
        }
    }else {
        resolve(x);
    }
}

class MyPromise {
    constructor(excuter) {
        this.status = PENDING;
        this.value  = undefined;
        this.reason = undefined;
        this.onFulfilledCallbacks = []
        this.onRejectedCallbacks = []
        
        const resolve = (value) =&gt; {
            // 2.1.2
            if(this.status === PENDING) {
                this.status = FULFILLED;
                this.value = value
                 
                // 发布
                this.onFulfilledCallbacks.forEach(cb =&gt; cb());
            }

        }
        const reject = (reason) =&gt; {
            // 2.1.3
            if(this.status === PENDING) {
                this.status = REJECTED;
                this.reason = reason

                // 发布
                this.onRejectedCallbacks.forEach(cb =&gt; cb());
            }

        }

        try {
            excuter(resolve, reject);
        } catch (e) {
            reject(e);
        }
    }

    // 2.2 the then method
    then(onFulfilled, onRejected) {
        onFulfilled = typeof onFulfilled == 'function'?onFulfilled: value =&gt; value;
        onRejected = typeof onRejected == 'function'?onRejected: reason =&gt; { throw reason };

        let promise2 = new MyPromise((resolve, reject) =&gt; {
            if(this.status == FULFILLED) {
                setTimeout(() =&gt; {
                    try {
                        let x = onFulfilled(this.value);
                        resolvePromise(promise2, x, resolve, reject);
                    } catch (e) {   
                        reject(e);
                    }
                }, 0);
            }
            if(this.status == REJECTED) {
                setTimeout(() =&gt; {
                    try {
                        let x = onRejected(this.reason);
                        resolvePromise(promise2, x, resolve, reject);
                    } catch (e) {   
                        reject(e);
                    }
                }, 0);
            }
            if(this.status == PENDING) {
                // 订阅
                this.onFulfilledCallbacks.push(() =&gt; {
                    try {
                        let x = onFulfilled(this.value);
                        resolvePromise(promise2, x, resolve, reject);
                    } catch (e) {   
                        reject(e);
                    }
                })
                this.onRejectedCallbacks.push(() =&gt; {
                    try {
                        let x = onRejected(this.reason);
                        resolvePromise(promise2, x, resolve, reject);
                    } catch (e) {   
                        reject(e);
                    }
                })
            }
        });
        return promise2;
    }

    catch(cb) {
        return this.then(null, cb);
    }
}
</code></pre>

            </div>
            
            
              <div class="next-post">
                <div class="next">下一篇</div>
                <a href="https://tingke.gitee.io/post/post/iZ7sJPRIL/">
                  <h3 class="post-title">
                    前端基础之CSS属性
                  </h3>
                </a>
              </div>
            

            

          </div>

        </div>
      </div>
    </div>

    <script src="https://unpkg.com/aos@next/dist/aos.js"></script>

<script type="application/javascript">

AOS.init();

hljs.initHighlightingOnLoad()

var app = new Vue({
  el: '#app',
  data: {
    menuVisible: false,
  },
})

</script>




  </body>
</html>
